﻿//ProtectiveStrategy.cs
//Copyright (c) 2013 StockSharp LLC, all rights reserved.
//This code module is part of StockSharp library.
//This code is licensed under the GNU GENERAL PUBLIC LICENSE Version 3.
//See the file License.txt for the license details.
//More info on: http://stocksharp.com

namespace StockSharp.Algo.Strategies
{
	using System;
	using System.Linq;

	using Ecng.Common;
	using Ecng.ComponentModel;

	using StockSharp.BusinessEntities;
	using StockSharp.Logging;

	/// <summary>
	/// Базовая стратегия защиты позиции.
	/// </summary>
	public abstract class ProtectiveStrategy : QuotingStrategy, IProtectiveStrategy
	{
		private bool _quotingStarted;

		/// <summary>
		/// Инициализировать <see cref="ProtectiveStrategy"/>.
		/// </summary>
		/// <param name="trade">Защищаемая сделка.</param>
		/// <param name="protectiveLevel">
		/// Защитный уровень. Если тип <see cref="Unit.Type"/> равен <see cref="UnitTypes.Limit"/>,
		/// то задается конкретная цена. Иначе, указывается величина сдвига от защищаемой сделки <paramref name="trade"/>.
		/// </param>
		protected ProtectiveStrategy(MyTrade trade, Unit protectiveLevel)
			: base(trade.Order.Direction.Invert(), trade.Trade.Volume)
		{
			if (trade == null)
				throw new ArgumentNullException("trade");

			_protectiveLevel = this.Param("ProtectiveLevel", new Unit());
			_useQuoting = this.Param<bool>("UseQuoting");
			_bestPriceOffset = this.Param("BestPriceOffset", new Unit());
			_priceOffset = this.Param("PriceOffset", new Unit());
			_isTrailing = this.Param<bool>("IsTrailing");

			Trade = trade;
			ProtectiveLevel = protectiveLevel;
		}

		/// <summary>
		/// Защищаемый объем.
		/// </summary>
		public decimal ProtectiveVolume
		{
			get { return LeftVolume; }
			set
			{
				QuotingVolume = value - Position.Abs();
				ProtectiveVolumeChanged.SafeInvoke();
			}
		}

		/// <summary>
		/// Событие об изменении защищаемого объема.
		/// </summary>
		public event Action ProtectiveVolumeChanged;

		/// <summary>
		/// Защищаемая сделка.
		/// </summary>
		public MyTrade Trade { get; private set; }

		private readonly StrategyParam<Unit> _protectiveLevel;

		/// <summary>
		/// Защитный уровень. Если тип <see cref="Unit.Type"/> равен <see cref="UnitTypes.Limit"/>,
		/// то задается конкретная цена. Иначе, указывается величина сдвига от защищаемой сделки <see cref="Trade"/>.
		/// </summary>
		public Unit ProtectiveLevel
		{
			get { return _protectiveLevel.Value; }
			set
			{
				_protectiveLevel.Value = value;

				if (ProcessState == ProcessStates.Started)
					ProcessQuoting();
			}
		}

		private readonly StrategyParam<bool> _isTrailing;

		/// <summary>
		/// Использовать ли механизм скольжения.
		/// Для <see cref="TakeProfitStrategy"/> при увеличении прибыли автоматически увеличивается уровень взятия прибыли.
		/// Для <see cref="StopLossStrategy"/> при увеличении прибыли автоматически увеличивается уровень защиты убытка.
		/// По-умолчанию выключено.
		/// </summary>
		public bool IsTrailing
		{
			get { return _isTrailing.Value; }
			set
			{
				if (value && ProtectiveLevel.Type == UnitTypes.Limit)
					throw new ArgumentException("Механизм скольжения не может быть использовать при лимитированном уровне.");

				_isTrailing.Value = value;
			}
		}

		/// <summary>
		/// Абсолютное значение цены, при достижении которой автивизируется защитная стратегия. Если цена равна 0, то активация не требуется.
		/// </summary>
		public abstract decimal ActivationPrice { get; }

		/// <summary>
		/// Активирована ли защитная стратегия.
		/// </summary>
		public bool IsActivated { get; private set; }

		/// <summary>
		/// Событие активации защитной стратегии.
		/// </summary>
		public event Action Activated;

		private readonly StrategyParam<bool> _useQuoting;

		/// <summary>
		/// Котировать ли выставленную заявку по рыночной цене. По-умолчанию режим выключен.
		/// </summary>
		public bool UseQuoting
		{
			get { return _useQuoting.Value; }
			set { _useQuoting.Value = value; }
		}

		private readonly StrategyParam<Unit> _bestPriceOffset;

		/// <summary>
		/// Отступ от лучшей цены, на которую может уйти котируемая заявка.
		/// </summary>
		public Unit BestPriceOffset
		{
			get { return _bestPriceOffset.Value; }
			set { _bestPriceOffset.Value = value; }
		}

		private readonly StrategyParam<Unit> _priceOffset;

		/// <summary>
		/// Отступ цены для выставляемой заявки. Определяет размер отступа от лучшей котировки (для покупки прибавляется к цене, для продажи - вычитается).
		/// </summary>
		public Unit PriceOffset
		{
			get { return _priceOffset.Value; }
			set { _priceOffset.Value = value; }
		}

		/// <summary>
		/// Объем, который необходимо скотировать.
		/// </summary>
		public override decimal QuotingVolume
		{
			set
			{
				if (UseQuoting)
				{
					foreach (var childStrategy in ChildStrategies.OfType<QuotingStrategy>())
					{
						childStrategy.QuotingVolume = value;
					}
				}
				else
				{
					base.QuotingVolume = value;
				}
			}
		}

		/// <summary>
		/// Направление защищаемой позиции.
		/// </summary>
		public OrderDirections ProtectiveDirection
		{
			get { return Trade.Order.Direction; }
		}

		/// <summary>
		/// Базовая цена защищаемой позиции, от которой будет рассчитываться <see cref="ProtectiveLevel"/> для определения <see cref="ActivationPrice"/>.
		/// </summary>
		public virtual decimal BasePrice
		{
			get { return Trade.Trade.Price; }
		}

		/// <summary>
		/// Метод вызывается тогда, когда вызвался метод <see cref="Strategy.Start"/>, и состояние <see cref="Strategy.ProcessState"/> перешло в значение <see cref="ProcessStates.Started"/>.
		/// </summary>
		protected override void OnStarted()
		{
			this.AddInfoLog("Защита сделки {0} заявки {1}.", Trade.Trade.Id, Trade.Order.TransactionId);
			base.OnStarted();
		}

		/// <summary>
		/// Рыночная цена закрытия позиции.
		/// </summary>
		protected decimal ClosePositionPrice
		{
			get { return Security.GetMarketPrice(ProtectiveDirection.Invert()); }
		}

		/// <summary>
		/// Обработчик события наступления <see cref="QuotingStrategy.TimeOut"/>.
		/// </summary>
		protected override void ProcessTimeOut()
		{
			ProcessQuoting();
		}

		/// <summary>
		/// Нужно ли котировать заявку.
		/// </summary>
		/// <param name="currentPrice">Текущая цена.</param>
		/// <param name="currentVolume">Текущий объем.</param>
		/// <param name="acceptablePriceRange">Приемлемый диапазон цены.</param>
		/// <param name="newVolume">Новый объем.</param>
		/// <returns>Цена, по которой будет выставлена заявка. Если возвращается 0, то заявка выставляться не будет.</returns>
		protected override decimal NeedQuoting(decimal currentPrice, decimal currentVolume, Range<decimal> acceptablePriceRange, decimal newVolume)
		{
			if (IsActivated)
			{
				// отдельное котирование работает со своей логикой перестановки
				if (UseQuoting)
					return 0;
				else
				{
					var price = ClosePositionPrice;

					// при использовании скользящего стопа, цена будет изменяться, и нужно будет перекотировать.
					// в противном случае этого делать не нужно
					return price == currentPrice ? 0 : price;
				}
			}
			else
			{
				var price = (ProtectiveLevel.Type == UnitTypes.Limit) ? (decimal)ProtectiveLevel : ActivationPrice;

				if (price == 0)
					return 0;

				RaiseActivated();

				if (!UseQuoting)
					return price;

				this.AddInfoLog("Регистрация защитного котирования.");

				_quotingStarted = true;

				var quoting = CreateQuoting();

				quoting.Name = Name + "_" + quoting.Name;

				quoting
					.WhenStopped()
					.Do(Stop)
					.Once()
					.Apply(this);

				ChildStrategies.Add(quoting);

				//Если включен режим котирования, то после запуска стратегии котирования
				//нет необходимости выставлять заявку в базовой стратегии.
				return 0;
			}
		}

		/// <summary>
		/// Инициировать котирование.
		/// </summary>
		protected override void ProcessQuoting()
		{
			if (UseQuoting && _quotingStarted)
				return;

			base.ProcessQuoting();
		}

		/// <summary>
		/// Зарегистрировать котируемую заявку.
		/// </summary>
		/// <param name="order">Котируемая заявка.</param>
		protected override void RegisterQuotingOrder(Order order)
		{
			if (order.Security.ExchangeBoard.IsSupportMarketOrders)
				order.Type = OrderTypes.Market;

			order.Comment += "{0} {1}".Put(NameGenerator.ShortName, Trade.Trade.Id);

			base.RegisterQuotingOrder(order);
		}

		/// <summary>
		/// Создать стратегию котирования для защитной заявки (гарантированно исполнить ее).
		/// </summary>
		/// <returns>Стратегия котирования.</returns>
		protected virtual QuotingStrategy CreateQuoting()
		{
			return new MarketQuotingStrategy(QuotingDirection, ProtectiveVolume)
			{
				BestPriceOffset = BestPriceOffset,
				PriceOffset = PriceOffset,
			};
		}

		///// <summary>
		///// Нужно ли активировать защитную стратегию.
		///// </summary>
		///// <returns>True, если требуется активация. Иначе, false.</returns>
		//protected abstract bool NeedActivate();

		/// <summary>
		/// Вызвать событие <see cref="Activated"/>.
		/// </summary>
		protected virtual void RaiseActivated()
		{
			IsActivated = true;
			this.AddInfoLog("Защита активирована.");
			Activated.SafeInvoke();
		}
	}
}